package com.baidu.iit.pxp.el;

import com.baidu.iit.pxp.el.juel.*;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.PrintWriter;

/**
 * User: huangweili
 * Date: 14-4-29
 * Time: 下午2:16
 */
public final class TreeValueExpression extends ValueExpression {
    private static final long serialVersionUID = 1L;

    private final TreeBuilder builder;
    private final Bindings bindings;
    private final String expr;
    private final Class<?> type;
    private final boolean deferred;

    private transient ExpressionNode node;

    private String structure;


    public TreeValueExpression(TreeStore store, FunctionMapper functions, VariableMapper variables, TypeConverter converter, String expr, Class<?> type) {
        super();

        Tree tree = store.get(expr);

        this.builder = store.getBuilder();
        this.bindings = tree.bind(functions, variables, converter);
        this.expr = expr;
        this.type = type;
        this.node = tree.getRoot();
        this.deferred = tree.isDeferred();

        if (type == null) {
            throw new NullPointerException("Expected type must not be null");
        }
    }

    private String getStructuralId() {
        if (structure == null) {
            structure = node.getStructuralId(bindings);
        }
        return structure;
    }

    @Override
    public Class<?> getExpectedType() {
        return type;
    }

    @Override
    public String getExpressionString() {
        return expr;
    }


    @Override
    public Class<?> getType(ELContext context) throws ELException {
        return node.getType(bindings, context);
    }


    @Override
    public Object getValue(ELContext context) throws ELException {
        return node.getValue(bindings, context, type);
    }


    @Override
    public boolean isReadOnly(ELContext context) throws ELException {
        return node.isReadOnly(bindings, context);
    }


    @Override
    public void setValue(ELContext context, Object value) throws ELException {
        node.setValue(bindings, context, value);
    }


    @Override
    public boolean isLiteralText() {
        return node.isLiteralText();
    }

    @Override
    public ValueReference getValueReference(ELContext context) {
        return node.getValueReference(bindings, context);
    }


    public boolean isLeftValue() {
        return node.isLeftValue();
    }


    public boolean isDeferred() {
        return deferred;
    }


    @Override
    public boolean equals(Object obj) {
        if (obj != null && obj.getClass() == getClass()) {
            TreeValueExpression other = (TreeValueExpression)obj;
            if (!builder.equals(other.builder)) {
                return false;
            }
            if (type != other.type) {
                return false;
            }
            return getStructuralId().equals(other.getStructuralId()) && bindings.equals(other.bindings);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return getStructuralId().hashCode();
    }

    @Override
    public String toString() {
        return "TreeValueExpression(" + expr + ")";
    }


    public void dump(PrintWriter writer) {
        NodePrinter.dump(writer, node);
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        try {
            node = builder.build(expr).getRoot();
        } catch (ELException e) {
            throw new IOException(e.getMessage());
        }
    }
}
